﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;

namespace TracertCMD
{
    //TODO:完成结束跟踪相关的代码以及测试该类

    public class TracertTemp
    {
        //事件
        public event EventHandler TraceFinished;
        public event EventHandler TraceCanceled;
        public event EventHandler<IPAddress> TraceInitFinished;
        public event EventHandler<TraceEventArgs> TraceUpdate;

        //事件参数类
        public class TraceEventArgs : EventArgs
        {
            public int TTL { private set; get; }
            public double AverageDelay { private set; get; }//平均延时，单位：毫秒
            public List<PingReply> PingReplies { private set; get; }//多次Ping的返回结果

            public TraceEventArgs(int ttl, List<PingReply> pingReplies)
            {
                TTL = ttl;
                PingReplies = pingReplies;
                AverageDelay = GetAverageDelay(pingReplies);
            }

            //计算平均延时
            private static double GetAverageDelay(List<PingReply> pingReplies)
            {
                double sum = 0;
                int count = 0;
                foreach (var item in pingReplies)
                {
                    if (item.Status == IPStatus.TtlExpired || item.Status == IPStatus.Success)
                    {
                        count++;
                        sum += item.RoundtripTime;
                    }
                }
                if (count == 0)
                {
                    return -1.0;
                }
                return sum / count;
            }
        }

        //公有属性
        public bool IsTracing { private set; get; } = false;//是否正在跟踪
        public List<TraceEventArgs> TraceList { private set; get; }
        public int PingTimes { set; get; } = 3;//每次跟踪Ping次数
        public int PingTimeout { set; get; } = 3000;//Ping超时时间，单位：毫秒
        public int MaxHops { set; get; } = 30;//最大跃点数
        public IPAddress TraceIPAddress { private set; get; }

        //私有变量
        private bool canceled = false;//是否中断Ping，当在Ping时检测到该值为true时将停止后续的Ping。
        private bool isTracing;
        private Task traceTask;

        public void BeginTrack(string ipOrDomain)
        {
            if (IsTracing)
            {
                throw new Exception("当前存在正在跟踪的任务，请先调用CancelTrack()结束跟踪。");
            }

            Task.Run(() =>
            {
                //初始化
                IsTracing = true;
                canceled = false;
                TraceList = new List<TraceEventArgs>();

                //获取目标IP地址
                TraceIPAddress = ParseDomainToIPv4(ipOrDomain);
                if (TraceIPAddress == null)
                {
                    IsTracing = false;
                    throw new ArgumentException("IP地址或域名格式错误！");
                }
                TraceInitFinished?.Invoke(this, TraceIPAddress);

                //开始跟踪
                traceTask = AsyncTrace(TraceIPAddress);
            });
        }

        public void CancelTrack()
        {
            if (!IsTracing)
            {
                return;
            }
            canceled = true;
        }

        /// <summary>
        /// 将域名解析为IPv4地址，如果传入的为IPv4格式地址，则直接将该地址封装为IPAddress类返回。
        /// </summary>
        /// <param name="input">域名或IP地址</param>
        /// <returns></returns>
        private static IPAddress ParseDomainToIPv4(string input)
        {
            //检查输入的是IP地址还是域名，如果为域名则解析其IP地址
            if (!IPAddress.TryParse(input, out IPAddress ip))
            {
                Regex regEx = new Regex(@"\d+\.\d+\.\d+\.\d+");

                //根据DNS解析该域名解析到的IP地址
                IPHostEntry hostEntry = Dns.GetHostEntry(input);
                foreach (IPAddress ipAddr in hostEntry.AddressList)
                {
                    if (regEx.IsMatch(ipAddr.ToString()))//检查是否为IPv4格式
                    {
                        ip = ipAddr;
                        break;
                    }
                }
            }

            return ip;
        }

        private async Task AsyncTrace(IPAddress ip)
        {
            var icmp = new Ping();
            var ttl = 1;//当前TTL
            var buffer = new byte[32];
            Random random = new Random();
            
            while (true)
            {
                //发起Ping
                var pingReplies = new List<PingReply>();
                for (int i = 0; i < PingTimes; i++)
                {
                    if (canceled)
                    {
                        break;
                    }
                    var reply = await Task.Run(() => icmp.Send(ip, PingTimes, buffer, new PingOptions(ttl, false)));
                    pingReplies.Add(reply);
                }
                if (canceled)
                {
                    break;
                }

                //记录Ping结果
                var args = new TraceEventArgs(ttl, pingReplies);
                TraceList.Add(args);
                TraceUpdate?.Invoke(this, args);

                //检查是否已到达目标IP地址
                var addresses = (from n in pingReplies
                                 where n.Status == IPStatus.Success
                                 where n.Address.ToString() == TraceIPAddress.ToString()
                                 orderby n.RoundtripTime
                                 select n.Address).ToArray();
                if (addresses.Length > 0)//已经到达目的地
                {
                    break;
                }

                //检查是否已经达到最大跃点数
                ttl++;
                if (ttl > MaxHops)//已经到最大跃点数
                {
                    break;
                }
            }

            //检查结束的类型
            if (canceled)//用户设置canceled标志为true
            {
                TraceCanceled?.Invoke(this, null);
            }
            else//正常结束
            {
                TraceFinished?.Invoke(this, null);
            }
            IsTracing = false;
        }
    }
}
